<template>
  <div ref="canvas"></div>
</template>

<script lang="ts" setup>
import { initCamera, initWebGLRenderer, initStat } from "@/book/ThreeUtils";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";
import { ref, onMounted } from "vue";

// 画布
const canvas = ref<any>('');
// 场景
const scene = new THREE.Scene();
// 相机
const camera = initCamera(undefined);
// 渲染器
const webGLRenderer = initWebGLRenderer({});
// 性能统计
const stats = initStat(0);
// 相机控件
const orbitControl = new OrbitControls(camera, webGLRenderer.domElement);
// plane
const planeGeometry = new THREE.PlaneGeometry(60, 20, 120, 120);
const planeMaterial = new THREE.MeshLambertMaterial({
  color: 0xffffff,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.castShadow = true;
plane.receiveShadow = true;
plane.rotation.x = -0.5 * Math.PI;
plane.position.set(15, 0, 0);
// cube
const cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
const cubeMaterial = new THREE.MeshLambertMaterial({
  color: 0xff0000
});
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.castShadow = true;
cube.position.set(-4, 3, 0);
// sphere
const sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
const sphereMaterial = new THREE.MeshLambertMaterial({
  color: 0x7777ff
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true;
sphere.position.set(20, 0, 2);
// 添加到场景中
scene.add(plane, cube, sphere);
// 环境光
const ambient = new THREE.AmbientLight("#1c1c1c");
// 聚光灯
const planeSpot = new THREE.SpotLight(0xcccccc);
planeSpot.position.set(-40, 30, -10);
planeSpot.lookAt(plane.position);

const target = new THREE.Object3D();
target.position.set(5, 0, 0);

// 通过distance定义了光锥的长度，angle定义了光锥的角度，二者一起则确定了光锥的形状
const spot = new THREE.SpotLight("#ffffff");
spot.position.set(-40, 60, -10);
spot.castShadow = true;
spot.target = plane;
spot.distance = 0;
spot.angle = 0.4;
spot.shadow.camera.near = 1; // 投影近点 - 从距离光源哪个位置开始可以生成阴影 默认 50
spot.shadow.camera.far = 100; // 投影远点 - 到距离光源哪个位置为止都可以生成阴影，默认为 5000
spot.shadow.camera.fov = 120; // 投影视场
// spot.shadow.bias = 0.01; // 阴影偏移，如果出现渲染失真或渲染出很奇怪的阴影效果时，可以尝试将该属性设置为很小的值(如: 0.01，默认为0)
// 如果阴影看上去有点粗糙（如阴影形状的边缘呈块状），可以增加shadow.mapSize.width和shadow.mapSize.height属性的值，或者保证用于计算阴影的区域紧密包围在对象周围
// 如果想要阴影更柔和，可以在THREE.WebGLRenderer对象上设置不同的shadowMap-Type属性值

// 添加到场景中
scene.add(ambient, planeSpot, spot);
// 相机辅助坐标
const debugCameraHelper = new THREE.CameraHelper(spot.shadow.camera);
// 聚光灯辅助坐标
const spotLightHelper = new THREE.SpotLightHelper(spot);
// 添加到场景
scene.add(spotLightHelper);

// 用一个小球来模拟点光源
const sphereLightGeometry = new THREE.SphereGeometry(0.2);
const sphereLightMaterial = new THREE.MeshLambertMaterial({
  color: 0xac6c25,
});
const sphereLight = new THREE.Mesh(sphereLightGeometry, sphereLightMaterial);
sphereLight.castShadow = true;
sphereLight.position.set(3, 20, 3);
scene.add(sphereLight);

// 渲染函数
const render = () => {
  stats.update();
  orbitControl.update();

  // 立方体旋转控制
  cube.rotation.x += controlObj.rotationSpeed;
  cube.rotation.z += controlObj.rotationSpeed;
  cube.rotation.y += controlObj.rotationSpeed;

  // 球体弹跳控制
  step += controlObj.bouncingSpeed;
  sphere.position.x = 20 + (10 * Math.cos(step));
  sphere.position.y = 2 + (10 * Math.abs(Math.sin(step)));

  // 模拟点光源移动
  if (!controlObj.stopMovingLight) {
    if (phase > 2 * Math.PI) {
      invert *= -1;
      phase -= 2 * Math.PI;
    } else {
      phase += controlObj.rotationSpeed;
    }

    sphereLight.position.z = +(7 * (Math.sin(phase)));
    sphereLight.position.x = +(14 * (Math.cos(phase)));
    sphereLight.position.y = 15;

    if (invert < 0) {
      const pivot = 14;
      sphereLight.position.x = (invert * (sphereLight.position.x - pivot)) + pivot;
    }
    spot.position.copy(sphereLight.position);
  }
  spotLightHelper.update();

  requestAnimationFrame(render);
  webGLRenderer.render(scene, camera);
};

onMounted(() => {
  canvas.value.appendChild(stats.domElement);
  canvas.value.appendChild(webGLRenderer.domElement);
  render();
});

// 步长
let step = 0;
let invert = 1;
let phase = 0;

// GUI
const controlObj = {
  rotationSpeed: 0.03, // 旋转速度
  bouncingSpeed: 0.03, // 弹跳速度
  ambientColor: ambient.color.getStyle(), // 环境光颜色
  pointColor: spot.color.getStyle(), // 聚光灯颜色
  intensity: 1, // 聚光灯光强度
  distance: 0, // 聚光灯光源照射距离 默认值为0，意思是光线强度不会随着距离增加而减弱
  angle: 0.1, // 聚光灯角度
  shadowDebug: false, // 聚光灯阴影调试模式
  castShadow: true, // 聚光灯启用阴影
  target: 'plane', // 聚光灯照射目标
  stopMovingLight: false, // 聚光灯停止移动
  penumbra: 0, // 聚光灯半影区 照明区域边缘附件的光照衰减速度
};
const gui = new GUI();
gui.addColor(controlObj, 'ambientColor').name("环境光颜色").onChange((e) => {
  ambient.color = new THREE.Color(e);
});
const folder = gui.addFolder('聚光灯');
folder.addColor(controlObj, 'pointColor').name("颜色").onChange((e) => {
  spot.color = new THREE.Color(e);
});
// @ts-ignore
folder.add(controlObj, 'angle', 0, Math.PI * 2).step(0.01).name("角度").onChange((e) => {
  spot.angle = e;
});
// @ts-ignore
folder.add(controlObj, 'intensity', 0, 5).step(0.1).name("强度").onChange((e) => {
  spot.intensity = e;
});
// @ts-ignore
folder.add(controlObj, 'penumbra', 0, 1).step(0.01).name("半影区").onChange((e) => {
  spot.penumbra = e;
});
// @ts-ignore
folder.add(controlObj, 'distance', 0, 200).step(10).name("光源照射距离").onChange((e) => {
  spot.distance = e;
});
// @ts-ignore
folder.add(controlObj, 'shadowDebug').name("阴影调试模式").onChange((e) => {
  if (e) {
    scene.add(debugCameraHelper);
  } else {
    scene.remove(debugCameraHelper);
  }
});
// @ts-ignore
folder.add(controlObj, 'castShadow').name("是否启用阴影").onChange((e) => {
  spot.castShadow = e;
});
// @ts-ignore
folder.add(controlObj, 'target', ['Plane', 'Sphere', 'Cube', 'Other']).name("照射目标").onChange((e) => {
  switch (e) {
    case "Plane":
      spot.target = plane;
      break;
    case "Sphere":
      spot.target = sphere;
      break;
    case "Cube":
      spot.target = cube;
      break;
    case "Other":
      spot.target = target;
      break;
  }
});
// @ts-ignore
folder.add(controlObj, 'stopMovingLight').name("是否停止移动").onChange((e) => {
  controlObj.stopMovingLight = e;
});

</script>
